# -*- coding: utf-8 -*-

import os
import sys

if __name__ == "__main__":
    currentdir = os.path.dirname( os.path.abspath( sys.argv[0] ) )
    basedir = os.path.abspath( os.path.join( currentdir, ".." ) )
    pythondir = basedir
    sys.path.insert( 0, pythondir )

import types
import random
from jsonrpc.jmsghandler import RequestHandler
from jsonrpc.sendmsg import sendjmsg
from jsonrpc import Server
from time import sleep
from time import time

__all__ = ['HashHandler', 'HashServer', 'HashRequest', 'HashManager']

#---------------------------------------------

class HashHandler(RequestHandler):
    """
    Do some tasks, maybe in subprocess
    """
    
    def _json_handle(self, json_data):
        """
        @param json_data: simple object data, after json.loads
        @type: mixed
        
        @return: python simple object data, jsonable via json.dumps
        @rtype: mixed
        """
        result = False
        
        if not (type(json_data) is types.DictionaryType):
            return result

        if json_data["task"] == "ping":
            return "pong"
        if json_data["task"] == "quit":
            return self.server.quit()
        if json_data["task"] == "hash":
            json_data = json_data["data"]
            return self.server.hash(
                json_data["file_id"],
                json_data["device"],
                json_data["file_size"],
                json_data["full_path"]
            )
        return result
    
    pass

#---------------------------------------------

class HashServer(Server):
    
    queue = None
    stop_event = None
    task_service_address = None
    _worker_thread = None

    def _worker ( self ):

        print("HashServer: START: %s" % (self.server_address,))

        import hashlib
        from project.alchemy import Project
        from service.task import TaskManager

        p = Project()
        p.connect()
        sess = p.get_session()

        task_manager = TaskManager(self.task_service_address)

        stats_req = task_manager.get_request().getStatistics()
        options_req = task_manager.get_request().getOptions()

        hash_func = options_req.get("hash_function")

        while not self.stop_event.isSet():

            try:
                data = self.queue.get_nowait()
            except Exception:
                data = None

            if data is None:
                sleep(0.01)
                continue

            file_id, device, file_size, full_path = data

            f = None
            shash = None
            try:
                stats_req.set_hashing( device, full_path, file_size, 0, time() )

                alg = hashlib.new(hash_func)


                st = os.lstat(full_path)
                read_chunk = st.st_blksize * 32

                f = open( full_path, "rb" )

                rs = 0
                t_last = time()
                while 1:
                    if self.stop_event.isSet():
                        break
                    rd = f.read( read_chunk )
                    rds = len(rd)
                    if rds > 0:
                        alg.update(rd)
                        rs += rds
                        if time() - t_last >= 0.1:
                            t_last = time()
                            stats_req.inc("real_readed_size", rs )
                            stats_req.inc("readed_size", rs )
                            stats_req.inc_hashing(device, rs )
                            rs = 0
                    else:
                        break
                f.close()
                f = None
                stats_req.inc("real_readed_size", int(rs) )
                stats_req.inc("readed_size", int(rs) )
                stats_req.inc_hashing(device, int(rs) )
                shash = alg.hexdigest()
            except Exception:
                import traceback
                print("HashServer: ERROR!\n File: '%s'\n%s" % (
                    full_path, traceback.format_exc()))
                if f:
                    f.close()

            if shash:
                hitem = p.find_hash( shash )
                if hitem:
                    stats_req.inc("nonunique_count", 1)
                    stats_req.inc("nonunique_size", file_size)
                    stats_req.inc("nonunique_count_now", 1)
                    stats_req.inc("nonunique_size_now", file_size)

                    p.update_hash( hitem, file_id )
                else:
                    stats_req.inc("unique_count", 1)
                    hitem = p.insert_hash( file_id, shash )

                sess.expunge(hitem)

                stats_req.inc("hashed_count", 1)
            else:
                stats_req.inc("errors_count", 1)

            stats_req.inc("files_processed", 1)

            self.queue.task_done()

            p.get_session().close()

        p.close_session()

        print("HashServer: STOP: %s" % (self.server_address,))

        self.stop_event.clear()
        return

    def server_activate(self):
        from Queue import Queue
        from threading import Thread, Event

        self.queue = Queue()
        self.stop_event = Event()
        self._worker_thread = Thread(target=self._worker)
        self._worker_thread.start()
        return Server.server_activate(self)

    def hash(self, file_id, device, file_size, full_path):
        self.queue.put_nowait((file_id, device, file_size, full_path,))
        return True

    def _quit_thread(self):
        sleep(0.05)
        self.server_close()
        return

    def quit(self):
        print("HashServer: quit event")

        self.stop_event.set()

        # Clean Queue

        while True:
            try:
                data = self.queue.get_nowait()
            except Exception:
                data = None

            if data is None:
                break

            self.queue.task_done()

        from threading import Thread

        th = Thread(target = self._quit_thread)
        th.daemon = True
        th.start()
        return

    pass

#---------------------------------------------

def run_hash_service(host, port, task_service_address):

    try:
        SERVER_ADDRESS = (host, int(port),)

        server = HashServer(SERVER_ADDRESS, HashHandler, bind_and_activate=False)
        if task_service_address:
            host, port = task_service_address.split(':')
            server.task_service_address = (host, int(port),)

        server.server_bind()
        server.server_activate()
        server.serve_forever()
    except Exception:
        pass

    return


#---------------------------------------------

class HashManager(object):

    host = "127.0.1.6"
    port_min = 10000
    port_max = 20000
    ports = None
    request = None

    task_service_address = None

    def __init__(self, task_service_address):
        self.ports = {}
        self.task_service_address = task_service_address
        pass

    def start_service(self, device):

        device = str(device)

        runing = False
        if device not in self.ports:

            port = random.randint(self.port_min, self.port_max)

            self.ports[device] = port

            from archive import which
            from subprocess import Popen

            Popen([which("python"), "service/hash.py",
                   "--host", "%s" % self.host,
                   "--port", "%s" % port,
                   "--task-service", "%s:%s" % self.task_service_address
            ])

            # WAIT WHILE SERVICE STARTS

            pings = 10
            t_begin = time()
            while not runing:
                pong = self.sendmsg(device, "ping", {})

                if pong == "pong":
                    runing = True

                pings -= 1
                if pings <= 0:
                    break

            if pings <= 0:
                del self.ports[device]
                print("HashManager: FAIL TO START service on (%s, %s) in %s sec" % (self.host, port, time() - t_begin,))
            else:
                print("HashManager: STARTED service on (%s, %s) in %s sec" % (self.host, port, time() - t_begin,))

        return runing

    def sendmsg(self, device, task, data):
        port = self.ports[str(device)]
        SERVER_ADDRESS = (self.host, port,)

        count = 5
        t_wait = 0.2

        result = None
        while count > 0:
            t_begin = time()
            try:
                result = sendjmsg( SERVER_ADDRESS, {"task":task, "data":data}, timeout=t_wait )
                break
            except Exception:
                result = None
                count -= 1
                t_end = time()
                if t_end - t_begin < t_wait:
                    sleep(t_wait - (t_end - t_begin))
        if count <= 0:
            print("HashManager: FAIL SEND MESSAGE: task=%s, data=%r" % (task, data,))
        return result

    def quit_all(self):
        result = True
        for device, port in self.ports.items():
            ret = self.sendmsg(device, "quit", {})
            if ret is None:
                result = False
            else:
                del self.ports[ device ]
        return result

    def get_request(self):
        if not self.request:
            self.request = HashRequest(self)
        return self.request

    pass


#---------------------------------------------


class HashRequest(object):

    manager = None

    def __init__(self, manager):
        self.manager = manager
        pass

    def hash(self, file_id, device, file_size, full_path):
        return self.manager.sendmsg( device, "hash", {
            "file_id" : file_id,
            "device" : device,
            "file_size" : file_size,
            "full_path" : full_path
        })

    def ping(self, device):
        return self.manager.sendmsg(device, "ping", {})

    def quit(self):
        return self.manager.quit_all()

    pass


#---------------------------------------------

def launch():

    from argparse import ArgumentParser

    parser = ArgumentParser(description='Run file hashing service.')
    parser.add_argument('--host', dest='host', action='store', type=str,
        help='Listen on TCP address')
    parser.add_argument('--port', dest='port', action='store', type=int,
        help='Listen on TCP port')
    parser.add_argument('--task-service', dest='task_service', action='store', type=str,
        help='Task service listen on TCP address')

    args = parser.parse_args()
    run_hash_service(args.host, args.port, args.task_service)

    return 0

if __name__ == "__main__":
    launch()
